#include "../src/ordinal.hh"

#include <cassert>
#include <iostream>

namespace
{
using namespace playerpiano;

struct LogPRatingTestDatum 
{ 
  struct
    {
      unsigned int      rating;
      unsigned int      true_label;
      float             loglambda;
      float             logbeta;
      float             tau[4];
    }                                   inputs;
  struct
    {
      float             logp;
      float             dlogbeta;
      float             ddlogbeta;
      float             dtau[4];
    }                                   outputs;
};

void
test_logprating (void)
{
  LogPRatingTestDatum data[] = 
    {{{0, 3, 0.155129,  0.012101, {0.0682151, 0.836322,  0.452838}}, {-4.59086, -3.50403, -3.72774, {1.17004, 0.957618,  0.492151}}}, {{2, 3, 0.77722,  0.194512, {0.964409, 0.0227534, 0.754836}}, {-0.425584,  0.277639, -0.0116264, {-0.000601953, -0.0354239, 0.880498}}}, {{1,  0, 0.156297,  0.339076, {0.412443, 0.710343,  0.901124}}, {-1.70417, -1.20002, -1.64861, {-1.33459, 0.00796697,  0.0000298949}}}, {{0, 3, 0.322206,  0.0243031, {0.99918, 0.0249958,  0.279128}}, {-5.12675, -4.25016, -4.62238, {1.40573, 1.33563,  0.771268}}}, {{3, 2, 0.912977,  0.804005, {0.57512, 0.614532,  0.757848}}, {-8.0694, -7.39955, -7.5998, {-0.0210496, -3.64867,  -5.56596}}}, {{1, 0, 0.791904,  0.506905, {0.778209, 0.305009,  0.0146846}}, {-4.69375, -4.64153, -4.8421, {-3.63134,  0.0000449445, 4.46809e-9}}}, {{2, 2, 0.312392,  0.813801, {0.282256, 0.259849, 0.156095}}, {-0.482547,  0.414023, -0.0492135, {-0.0212299, -0.928828, 0.251662}}}, {{1, 1,  0.474725,  0.869813, {0.549506, 0.254971,  0.152519}}, {-0.817345, -0.030384, -0.256972, {-2.04854,  0.0937214, 0.000165103}}}, {{1, 0, 0.84551,  0.550326, {0.229976, 0.873392,  0.932533}}, {-2.99902, -2.80094, -3.21296, {-3.83708,  0.0000138426, 1.32171e-11}}}, {{3, 3, 0.746321,  0.654856, {0.25886, 0.174685,  0.954417}}, {-1.99562, -1.57282, -2.07621, {-1.79733e-6,  -0.0160808, -3.50811}}}};

  for (unsigned int i = 0; i < sizeof (data) / sizeof (data[0]); ++i)
    {
      Ordinal ordinal (data[i].inputs.tau, 0, &data[i].inputs.loglambda, 0, 1, 1, 4);
      Rating rating = { 0, data[i].inputs.rating };
      float dlogbeta = drand48 ();
      float ddlogbeta = drand48 ();

      float logp = ordinal.logprating (rating, 
                                       data[i].inputs.true_label,
                                       exp (data[i].inputs.logbeta),
                                       dlogbeta,
                                       ddlogbeta);

      assert (fabs (logp - data[i].outputs.logp) <=
              1e-4 * (1 + fabs (logp) + fabs (data[i].outputs.logp)) ||
              (std::cerr << logp << " ?= " << data[i].outputs.logp << std::endl,
               0));

      assert (fabs (dlogbeta - data[i].outputs.dlogbeta) <=
              1e-4 * (1 + fabs (dlogbeta) + fabs (data[i].outputs.dlogbeta)) ||
              (std::cerr << dlogbeta << " ?= " << data[i].outputs.dlogbeta << std::endl,
               0));

      assert (fabs (ddlogbeta - data[i].outputs.ddlogbeta) <=
              1e-4 * (1 + fabs (ddlogbeta) + fabs (data[i].outputs.ddlogbeta)) ||
              (std::cerr << ddlogbeta << " ?= " << data[i].outputs.ddlogbeta << std::endl,
               0));

      std::vector<float> dtau (3, 69);
      float megadlogbeta;
      ordinal.dlogpratingdtau (&dtau[0],
                               megadlogbeta,
                               rating,
                               data[i].inputs.true_label,
                               exp (data[i].inputs.logbeta));

      assert (fabs (megadlogbeta - data[i].outputs.dlogbeta) <=
              1e-4 * (1 + fabs (megadlogbeta) + fabs (data[i].outputs.dlogbeta)) ||
              (std::cerr << megadlogbeta << " ?= " << data[i].outputs.dlogbeta << std::endl,
               0));

      for (unsigned int j = 0; j < 3; ++j)
        {
          assert (fabs (dtau[j] - data[i].outputs.dtau[j]) <=
                  1e-4 * (1 + fabs (dtau[j]) + fabs (data[i].outputs.dtau[j])) ||
                  (std::cerr << "dtau[" << j << "] = " << dtau[j] << " ?= " << data[i].outputs.dtau[j] << std::endl,
                   0));
        }
    }
}

struct UpdateTestDatum 
{ 
  struct
    {
      float             loglambda[4];
      float             tau[4 * 4];
      float             logbeta;
      float             pz[5];
      Rating            rating;
    }                                   inputs;
  struct
    {
      float             dloglambda[4];
      float             dtau[4 * 4];
    }                                   outputs;
};

void
test_update (void)
{
  UpdateTestDatum data[] = 
    {{{{0.249783, 0.142865, 0.0519285, 0.223707}, {0.721601, 0.809542,  0.51596, 0.95473, 0.289419, 0.804227, 0.688257, 0.292987,  0.311997, 0.694142, 0.560725, 0.877808, 0.399284, 0.421179,  0.526462, 0.622791},  0.85172, {0.93653, 0.345382, 0.528377, 0.601936, 0.793666}, {1,  2}}, {{0, -10.6682, 0, 0}, {0, 0, 0, 0, -2.63139, -4.57687,  2.67752, 1.3748, 0, 0, 0, 0, 0, 0, 0, 0}}}, {{{0.293454, 0.30467,  0.880336, 0.984124}, {0.777494, 0.34994, 0.590917, 0.179897,  0.089237, 0.0569533, 0.27892, 0.485755, 0.528512, 0.179145,  0.879637, 0.0645764, 0.00205028, 0.556354, 0.0279171, 0.128046},  0.656668, {0.0279769, 0.425981, 0.33438, 0.363214, 0.723306}, {3,  0}}, {{0, 0, 0, -37.7507}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  9.38242, 6.3781, 5.50588, 3.31904}}}, {{{0.545645, 0.350257,  0.585721, 0.373366}, {0.954728, 0.17036, 0.496484, 0.316413,  0.675808, 0.684605, 0.967972, 0.137268, 0.796171, 0.620029,  0.965922, 0.580914, 0.768254, 0.491983, 0.309254, 0.552937},  0.342273, {0.157602, 0.946039, 0.829631, 0.796628, 0.807346}, {3,  4}}, {{0, 0, 0, -20.7623}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, -1.62651, -3.09614, -4.55203, -6.38683}}}, {{{0.360319,  0.456264, 0.8419, 0.636985}, {0.863835, 0.139851, 0.166092,  0.95238, 0.895863, 0.00258338, 0.369921, 0.332351, 0.929941,  0.421669, 0.601667, 0.840368, 0.620688, 0.868732, 0.259394,  0.682766},  0.674648, {0.0391017, 0.462766, 0.875421, 0.31433, 0.582837}, {1,  1}}, {{0, -9.69616, 0, 0}, {0, 0, 0, 0, -1.24552, 5.04463,  2.52432, 1.16242, 0, 0, 0, 0, 0, 0, 0, 0}}}, {{{0.620866,  0.238435, 0.450495, 0.442986}, {0.454774, 0.286055, 0.554632,  0.440403, 0.0848529, 0.953704, 0.624691, 0.0187332, 0.483186,  0.113335, 0.00400311, 0.150001, 0.223791, 0.430569, 0.329355,  0.110899},  0.761025, {0.555149, 0.0150247, 0.528062, 0.140159, 0.316714}, {1,  0}}, {{0, -8.08743, 0, 0}, {0, 0, 0, 0, 2.93964, 1.5313, 1.01838,  0.695561, 0, 0, 0, 0, 0, 0, 0, 0}}}, {{{0.56453, 0.0850758,  0.685384, 0.0306586}, {0.00989742, 0.644673, 0.600531, 0.0769549,  0.385207, 0.62594, 0.117346, 0.96362, 0.381203, 0.475939,  0.893555, 0.53305, 0.0518488, 0.36504, 0.13253, 0.977902},  0.0368241, {0.836978, 0.992371, 0.661188, 0.472295, 0.751902}, {1,  3}}, {{0, -7.60992, 0, 0}, {0, 0, 0,  0, -1.20776, -2.3234, -2.99862, 0.331846, 0, 0, 0, 0, 0, 0, 0,  0}}}, {{{0.306987, 0.63053, 0.462397, 0.107229}, {0.706455,  0.553575, 0.0771905, 0.481289, 0.589109, 0.589955, 0.695987,  0.00534988, 0.695555, 0.0569049, 0.644138, 0.64031, 0.563025,  0.079003, 0.607314, 0.803332},  0.570654, {0.417815, 0.13502, 0.0514294, 0.263668, 0.787285}, {0,  4}}, {{-12.3962, 0, 0, 0}, {-1.1565, -1.39906, -1.64055, -2.98822,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, {{{0.672623, 0.9442,  0.557213, 0.23371}, {0.595432, 0.462911, 0.968103, 0.643754,  0.899445, 0.457561, 0.272549, 0.58685, 0.255307, 0.817251,  0.709523, 0.507847, 0.647993, 0.0139198, 0.138869, 0.090032},  0.512973, {0.96249, 0.875201, 0.302747, 0.84035, 0.0182902}, {0,  3}}, {{-32.7714, 0, 0, 0}, {-4.71286, -6.51262, -9.27181,  0.0319908, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, {{{0.317989,  0.0690374, 0.244918, 0.555379}, {0.349886, 0.425283, 0.345473,  0.0978176, 0.0773372, 0.838433, 0.0901665, 0.280566, 0.367814,  0.330587, 0.442174, 0.266646, 0.228945, 0.240555, 0.929201,  0.304156},  0.353743, {0.937807, 0.0888508, 0.285866, 0.0357543, 0.86877}, {3,  3}}, {{0, 0, 0, -15.2205}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, -2.07396, -2.77272, -3.49217, 1.22501}}}, {{{0.843933,  0.730487, 0.685869, 0.443487}, {0.498459, 0.632669, 0.608531,  0.605054, 0.408293, 0.352103, 0.240718, 0.274467, 0.966119,  0.0854566, 0.0117728, 0.0339123, 0.0369178, 0.781301, 0.65803,  0.0961049},  0.948067, {0.495435, 0.622275, 0.227335, 0.104134, 0.764948}, {0,  4}}, {{-59.9879, 0, 0,  0}, {-4.82581, -7.64861, -8.49722, -11.6977, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0}}}}; 

  for (unsigned int i = 0; i < sizeof (data) / sizeof (data[0]); ++i)
    {
      std::vector<float> tau (data[i].inputs.tau, data[i].inputs.tau + 4 * 4);
      std::vector<float> loglambda (data[i].inputs.loglambda,
                                    data[i].inputs.loglambda + 4);
      std::vector<float> gamma (4, 0);
      std::vector<float> chi (4, 0);
      Ordinal ordinal (&tau[0], &gamma[0], &loglambda[0], &chi[0], 1, 4, 5);

      std::vector<float> pz (data[i].inputs.pz, data[i].inputs.pz + 5);

      ordinal.update (1,
                      1,
                      0,
                      exp (data[i].inputs.logbeta),
                      pz,
                      data[i].inputs.rating);

      for (unsigned int j = 0; j < 4; ++j)
        {
          loglambda[j] -= data[i].inputs.loglambda[j];

          assert (fabs (loglambda[j] - data[i].outputs.dloglambda[j]) <=
                  1e-4 * (1 + fabs (loglambda[j]) + fabs (data[i].outputs.dloglambda[j])) ||
                  (std::cerr << loglambda[j] << " ?= " << data[i].outputs.dloglambda[j] << std::endl,
                   0));
        }

      for (unsigned int j = 0; j < 4 * 4; ++j)
        {
          tau[j] -= data[i].inputs.tau[j];

          assert (fabs (tau[j] - data[i].outputs.dtau[j]) <=
                  1e-4 * (1 + fabs (tau[j]) + fabs (data[i].outputs.dtau[j])) ||
                  (std::cerr << "tau[" << j << "] = " << tau[j] << " ?= " << data[i].outputs.dtau[j] << std::endl,
                   0));
        }

    }
}

}

int 
main (void) 
{
  srand48 (69);

  test_logprating ();
  test_update ();

  return 0;
}
